class Seiso::ImportMaster
  # Imports a node document into Seiso.
  #
  # Each node doc corresponds to a service instance. This importer compares the nodes in Seiso with the nodes in the
  # node doc and removes anything from Seiso that isn't in the node doc. Then it imports the nodes from the node doc
  # into Seiso.
  #
  # Author:: Willie Wheeler (mailto:wwheeler@expedia.com)
  # Author:: Chris Brown
  # Copyright:: Copyright (c) 2014-2016 Expedia, Inc.
  # License:: Apache 2.0
  class Importers::NodeImporter < Importers::BaseImporter

    def initialize(api, rest_util, resolver)
      self.api = api
      self.rest_util = rest_util
      self.mapper = Mappers::NodeMapper.new resolver
      self.repo_resource = api.nodes.get

      @validator = Validators::NodeValidator.new
      @nip_importer = Importers::NodeIpAddressImporter.new(api, rest_util)
      @search_resource = repo_resource.search.get

      @machine_search_resource = api.machines.search.get
    end

    def import(doc)
      @validator.validate doc
      doc_nodes = doc['items']
      si_key = doc['serviceInstance']
      si_proxy = api.serviceInstances.search.findByKey(key: si_key)
      si = rest_util.get_or_nil(si_proxy, si_key)

      if si.nil?
        raise Util::InvalidDocumentError.new "Service instance #{si_key} not found."
      end

      seiso_nodes = si.nodes.get
      delete_orphans(doc_nodes, seiso_nodes)
      import_items(doc_nodes, { 'serviceInstance' => si })
    end

    # Can't make these protected, because that prevents BaseImporter from seeing them.
#    protected

    # BaseImporter callback
    def to_search_params(doc_node, context)
      { 'name' => doc_node['name'] }
    end

    # BaseImporter callback
    def find_resource(search_params)
      @search_resource.findByName(name: search_params['name']).get
    end

    # BaseImporter callback
    def context_for(parent_resource, parent_context)
      {
        'serviceInstance' => parent_context['serviceInstance'],
        'node' => parent_resource
      }
    end

    # BaseImporter callback
    def import_children_of(doc_node, context)
      doc_nips = doc_node['ipAddresses']

      # If there aren't any IP addresses explicitly set, then derive one from the machine.
      if doc_nips.nil? || doc_nips.empty?
        doc_nips = create_default_doc_nips(doc_node, context)
      end

      @nip_importer.import_items(doc_nips, context)
    end

    private

    def delete_orphans(doc_nodes, seiso_nodes)
      doc_node_names = doc_nodes.map { |doc_node| doc_node['name'] }
      seiso_nodes.each do |seiso_node|
        orphan = !doc_node_names.include?(seiso_node.name)
        if orphan
          params = { 'name' => seiso_node.name }
          rest_util.delete(seiso_node, params)
        end
      end
    end

    def create_default_doc_nips(doc_node, context)
      node_name = doc_node['name']
      machine_name = doc_node['machine']
      machine_search_params = { 'name' => machine_name }
      machine_proxy = @machine_search_resource.findByName machine_search_params
      machine_resource = @rest_util.get_or_nil(machine_proxy, machine_search_params)

      if machine_resource.nil?
        err_msg = "Node #{node_name} references nonexistent machine #{machine_name}."
        fail Util::InvalidDocumentError.new err_msg
      end

      [ {
        'ipAddressRole' => 'default',
        'ipAddress' => machine_resource.body['ipAddress']
      } ]
    end
  end
end
